package kevin.framework.basic.common.user;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang3.StringUtils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import kevin.framework.basic.common.thread.JWTUserThreadHolder;
import kevin.framework.basic.common.thread.RequestThreadHolder;
import kevin.framework.basic.common.thread.ResponseThreadHolder;
import kevin.framework.basic.common.utils.MyUUID;
import kevin.framework.basic.common.utils.SysConfigParams;
import kevin.framework.basic.domain.Menu;
import kevin.framework.basic.domain.User;

/**
 * JWT登录方式
 **/
public class JWTLoginUserContext extends LoginUserContext {

	@Override
	public void setUpdateRate(int rate) {
		// TODO Auto-generated method stub
	}

	@Override
	public void setExpiry(int expiry) {
		// TODO Auto-generated method stub
	}

	@Override
	public int getExpiry() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public void updateExpiry(String key) throws Exception {
		// TODO Auto-generated method stub
	}

	/*** 服务端不保存登录用户数据 ***/
	@Override
	public String setLoginUser(LoginUser user) throws Exception {
		String key = MyUUID.getUUID();
		user.setUserKey(key);
		return key;
	}

	@Override
	public void logout() throws Exception {
		Cookie cookie = this.getCookie(SysConfigParams.getConfig("jwtCookieKey"));
		if (cookie != null) {
			cookie.setValue(null);  
            cookie.setMaxAge(0);// 立即销毁cookie  
            cookie.setPath("/");
            cookie.setHttpOnly(true);		
			HttpServletResponse response = ResponseThreadHolder.getResponse();
			response.addCookie(cookie);
		}
	}

	@Override
	public void logout(String sessionId) throws Exception {
		logout();
	}

	@Override
	public void setKeyValue(String key, Object obj) throws Exception {
		HttpSession session = this.getSession();
		session.setAttribute(key, obj);
	}

	@Override
	public Object getKeyValue(String key) throws Exception {
		HttpSession session = this.getSession();
		if(session == null) {
			return null;
		}		  
		return session.getAttribute(key);
	}
	@Override
	public void deleteKeyValue(String key) throws Exception {
		HttpSession session = this.getSession();
		if(session != null) {
			session.removeAttribute(key);
		}
	}
	/**
	 * 从token中获取用户
	 *优先重请求参数中获取token
	 **/
	@Override
	public LoginUser getCurrentUser() throws Exception {
		LoginUser user = JWTUserThreadHolder.getUser();
		if (user != null) {
			return user;
		}
		HttpServletRequest request = RequestThreadHolder.getRequest();		
		String tokenString = request.getParameter("token");
		if (StringUtils.isEmpty(tokenString)) {
			Cookie cookie = this.getCookie(SysConfigParams.getConfig("jwtCookieKey"));
			if (cookie != null) {
				tokenString = cookie.getValue();
			}
		}
		if (StringUtils.isEmpty(tokenString)) {
			return null;
		}
		Claims claims = getTokenBody(tokenString);
		if (isExpiration(claims)) {
			return null;
		}
		// userId account userName orgName loginTime
		String userId = String.valueOf(claims.get("userId"));
		String account = String.valueOf(claims.get("account"));
		String userName = String.valueOf(claims.get("userName"));
		String orgName = String.valueOf(claims.get("orgName"));
		String orgId = String.valueOf(claims.get("orgId"));			
		user = new LoginUser();
		user.setId(userId);
		user.setUserName(userName);
		user.setAccount(account);
		user.setOrgName(orgName);
		user.setOrgId(orgId);		
		loadUserAuthData(user);
		getUserMenus(user);
		JWTUserThreadHolder.setUser(user);
		return user;
	}

	// 创建token https://blog.csdn.net/qq_37636695/article/details/79265711

	/**
	 * @Title: createToken @Description: TODO @param user @param
	 *         ttlMillis @return @throws
	 */
	private String createToken(LoginUser user, int expMin) {
		
		SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;		
		
		// 创建payload的私有声明（根据特定的业务需要添加
		// userId account userName orgName loginTime
		Map<String, Object> claims = new HashMap<String, Object>();
		claims.put("userId", user.getId());
		claims.put("account", user.getAccount());
		claims.put("userName", user.getUserName());
		claims.put("orgId", user.getOrgId());
		claims.put("orgName", user.getOrgName());

		String key = SysConfigParams.getConfig("jwtKey");

		JwtBuilder builder = Jwts.builder() // 这里其实就是new一个JwtBuilder，设置jwt的body
				.setClaims(claims) // 如果有私有声明，一定要先设置这个自己创建的私有的声明，这个是给builder的claim赋值，一旦写在标准的声明赋值之后，就是覆盖了那些标准的声明的
				.setId(MyUUID.getUUID()) // 设置jti(JWT ID)：是JWT的唯一标识，根据业务需要，这个可以设置为一个不重复的值，主要用来作为一次性token,从而回避重放攻击。
				.setIssuedAt(new Date()) // iat: jwt的签发时间
				.setSubject(user.getId()) // sub(Subject)：代表这个JWT的主体，即它的所有人，这个是一个json格式的字符串，可以存放什么userid，roldid之类的，作为什么用户的唯一标志。
				.signWith(signatureAlgorithm, key);// 设置签名使用的签名算法和签名使用的秘钥

		Calendar c = Calendar.getInstance();
		c.add(Calendar.MINUTE, expMin);// 1分钟前
		Date expDate = c.getTime();
		builder.setExpiration(expDate); // 设置过期时间
		return builder.compact();

	}

	// 是否已过期
	private boolean isExpiration(Claims claims) {
		Calendar c = Calendar.getInstance();
		c.add(Calendar.MINUTE, -1);// 1分钟前
		Date now = c.getTime();
		return claims.getExpiration().before(now);
	}

	private Claims getTokenBody(String token) {
		String secrit = SysConfigParams.getConfig("jwtKey");
		return Jwts.parser().setSigningKey(secrit).parseClaimsJws(token).getBody();
	}

	/***
	 * 形成jwt数据写入Cookies
	 ***/
	@Override
	public String writeLoginCookies(LoginUser loginUser) throws Exception {
		Object expCfgObject = SysConfigParams.getConfig("jwtExpTime");
		int min = 30 ;
		if (expCfgObject != null) {
			min = Integer.parseInt(String.valueOf(expCfgObject)) ;
		}
		String token = createToken(loginUser, min);
		updateUserLoginTime(loginUser);
		this.responseCookie(SysConfigParams.getConfig("jwtCookieKey"), token, true);
		return token;
	}

	/** 重写，只需要加载角色 ***/
	@Override
	public LoginUser loadUserAuthData(User user) throws Exception {
		LoginUser loginUser = new LoginUser(user);
		loadUserAuthData(loginUser);
		return loginUser;
	}
	
	/**加载角色到loginUser中**/
	public void loadUserAuthData(LoginUser loginUser) throws Exception {		
		String ip = getIpAddr();
		loginUser.setLoginIp(ip);
		// 加载角色
		if (!loginUser.isSuperAdmin()) {
			this.loadUserRole(loginUser);
		}
		loginUser.setLang(SysConfigParams.lang);
	}
	
	/***
	 * 重写，需要从数据库读取用户菜单
	 ***/
	@Override
	public List<Menu> getUserMenus(LoginUser user) throws Exception {		
		Map<String, Object> param = new HashMap<String, Object>();
		param.put("isEnable", 1);
		param.put("isVisible", 1);
		Map<String, Menu> menuMap = getMenuService().selectMap(param);		
		return this.loadUserMenus(menuMap, user);
	}

}
